/*
Copyright 2007 Julian Asamer

This file is part of Fractalicious.

    Fractalicious is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Fractalicious is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Fractalicious.  If not, see <http://www.gnu.org/licenses/>.
*/


#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import <GraphicsServices/GraphicsServices.h>
#import "Dragable.h"
#import "FView.h"

//The nested distanceChecker enables multiple DistanceCheckers per Point using some fancy recursive mechanism.
//But there's still some bug somewhere in here...
@interface DistanceChecker : NSObject
{
	float distance;
	Point2D * controlPoint;
	Point2D * pos;
	float accel;	//The shown Position.
	//Point2D * unMappedPos;	//The "real" Position.
	DistanceChecker *nested;
}

-(id)initWithDistance: (float) d point: (Point2D *) p controlPoint: (Point2D *)c;

-(void)updateWithVector: (Vector2D *)v;

//-(void)mapPos;
//-(void)unMapPos;
-(float)factorForVector:(Vector2D *)v;

-(void)setNestedChecker: (DistanceChecker *)c;
-(DistanceChecker *)nestedChecker;
@end

@implementation DistanceChecker 
-(id)initWithDistance: (float) d point: (Point2D *) p controlPoint: (Point2D *)c {
	[super init];
	distance = d;
	controlPoint = c;
	pos = p;
	accel = 1;
	//unMappedPos = [[Point2D alloc] init];
	//[self unMapPos];
	return self;
}
-(void)updateWithVector: (Vector2D *)v {
	//[unMappedPos setCoords: [unMappedPos add: v]];
	//[self mapPos];
	[pos setCoords: [pos add: [v multiplyWith: [self factorForVector: v]]]];
}

//WTF! - You're right. This ist some serious recursive mathematical stuff.
-(float)factorForVector:(Vector2D *)v {
	float n = (nested == nil) ? 1 : [nested factorForVector: v];
	float mv = [v length];
	float acd = [[controlPoint to: pos] length];
	return n*pow(((-(distance-acd)*pow(M_E,-mv/distance)+distance-acd)/mv), 1/accel);
}

-(void)setAcceleration:(float)a {
	accel = a;
}

-(float) acceleration {
	return accel;
}

// //WTF! - You're right. I'm doing some kind of special mapping here - I don't know if there exists a matrix which can do this; but I don't think so...
// //If you want to know what the hell I'm doing here, you have two options: 1. think, 2. ask me.
// -(void)mapPos {
// 	float umLength = [[controlPoint to: unMappedPos] length];
// 	float mLength = -pow(M_E, (-umLength/distance))*distance+distance;
// 	[mappedPos setCoords: [controlPoint add: [[[controlPoint to: unMappedPos] unityVector] multiplyWith: mLength]]];
// }
// //Ok, I've got to help you a bit. This thing does that you can not drag a point so far that you'd get a proportion greater or equal to 1.
// -(void)unMapPos {
// 	float mLength = [[controlPoint to: mappedPos] length];
// 	float umLength = -distance*log((distance-mLength)/distance);
// 	[unMappedPos setCoords: [controlPoint add: [[[controlPoint to: mappedPos] unityVector] multiplyWith: umLength]]];
// }

-(void)setNestedChecker: (DistanceChecker *)c {
	if (nested==nil) {
		nested = c;
	} else {
		[nested setNestedChecker: c];
	}
}
-(DistanceChecker *) nestedChecker {
	return nested;
}

@end

@implementation DragablePoint

- (DragablePoint *)initWithx: (float) _x y: (float) _y {
	[super initWithx: _x y: _y];
	radius = 12;
	checker = nil;
	return self;
}

- (void)drawWithContext: (CGContextRef) context transform: trans {
	//NSLog(@"Drawing Point");
	CGContextSetFillColorWithColor(context, [FView colorWithRed:0.5f green: 0.5f blue: 1.0f alpha: 0.3f]);
	CGContextSetLineWidth(context, 1.5f);
	float color[] = {0.1f,0.1f,9.0f,0.9f};
	CGContextSetStrokeColor(context, color);
	NSPoint p = [trans transformPoint: [self nsPoint]];
	CGContextFillEllipseInRect(context, CGRectMake(p.x-radius, p.y-radius, radius*2, radius*2));
	CGContextStrokeEllipseInRect(context, CGRectMake(p.x-radius, p.y-radius, radius*2, radius*2));
}

- (void) addMaximumDistance:(float)d fromPoint: (Point2D *)p {
	if (checker == nil) {
		checker = [[DistanceChecker alloc] initWithDistance: d point: self controlPoint: p];
	}
	else {
		[checker setNestedChecker: [[DistanceChecker alloc] initWithDistance: d point: self controlPoint: p]];
	}
}
- (void) addMaximumDistance:(float)d fromPoint: (Point2D *)p withAcceleration: (float) a {
	if (checker == nil) {
		checker = [[DistanceChecker alloc] initWithDistance: d point: self controlPoint: p];
		[checker setAcceleration: a];
	}
	else {
		DistanceChecker *c = [[DistanceChecker alloc] initWithDistance: d point: self controlPoint: p];
		[c setAcceleration: a];
		[c setNestedChecker: checker];
	}
}

- (BOOL) pointInside: (Point2D *)p {
	return [self equals: p withMaxDistance: 32.0f];
}

-(void) handleMove: (Vector2D *)v {
	if (checker == nil) {
		NSLog(@"Checker is nil; falling back to linear mapping (bad stuff).");
		x+=v->vx/2;
		y+=v->vy/2;
	}
	[checker updateWithVector: v];

	if (delegate!=nil && [delegate respondsToSelector: @selector(pointChanged)]) {
		[delegate performSelector: @selector(pointChanged)];
	}
}
-(void) handleMouseUp {
	NSLog(@"Mouseup!");
	if (delegate!=nil && [delegate respondsToSelector: @selector(pointMouseUp)]) {
		[delegate performSelector: @selector(pointMouseUp)];
	}
}


- (void) setRadius: (float) r {
	radius = r;
}

- (float) radius {
	return radius;
}

- (void) setDelegate:(id) d {
	delegate = d;
}
- (id) delegate {
	return delegate;
}
@end

@implementation DragableLine

-(id)initWithDragablePoint: (DragablePoint *) _p andDragablePoint: (DragablePoint *) _q {
	[super initWithPoint: _p andPoint: _q];
	[_p setDelegate: self];
	[_q setDelegate: self];
	return self;
}
-(id)initWithPoint: (Point2D *) _p andDragablePoint: (DragablePoint *) _q {
	[super initWithPoint: _p andPoint: _q];
	[_q setDelegate: self];
	return self;
}
-(id)initWithDragablePoint: (DragablePoint *) _p andPoint: (Point2D *) _q {
	[super initWithPoint: _p andPoint: _q];
	[_p setDelegate: self];
	return self;
}

- (void) setDelegate:(id) d {
	delegate = d;
}
- (id) delegate {
	return delegate;
}

-(void) pointChanged {
	if ([delegate respondsToSelector: @selector(lineChanged)]) {
		[delegate performSelector: @selector(lineChanged)];
	}
}

- (void)drawWithContext: (CGContextRef) context transform: trans {}
-(void) handleMouseUp {}

- (BOOL) pointInside: (Point2D *)p {
	return NO;
}

@end